/* eslint-disable */
/*

MIT License http://en.wikipedia.org/wiki/MIT_License  
IN SHORT: It's free. You can use it commercially. Don't sue us.
  
Copyright (C) 2011 by Martin Kool and Q42
  
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE
*/

/*
  Live.js - One script closer to Designing in the Browser
  Written for Handcraft.com by Martin Kool (@mrtnkl).

  Version 4.
  Recent change: Made stylesheet and mimetype checks case insensitive.

  http://livejs.com
  http://livejs.com/license.txt (MIT)  
  @livejs

  Include live.js#css to monitor css changes only.
  Include live.js#js to monitor js changes only.
  Include live.js#html to monitor html changes only.
  Mix and match to monitor a preferred combination such as live.js#html,css  

  By default, just include live.js to monitor all css, js and html changes.
  
  Live.js can also be loaded as a bookmarklet. It is best to only use it for CSS then,
  as a page reload due to a change in html or css would not re-include the bookmarklet.
  To monitor CSS and be notified that it has loaded, include it as: live.js#css,notify
*/
;(function () {
  var headers = {
      Etag: 1,
      'Last-Modified': 1,
      'Content-Length': 1,
      'Content-Type': 1,
    },
    resources = {},
    pendingRequests = {},
    currentLinkElements = {},
    oldLinkElements = {},
    interval = 1000,
    loaded = false,
    active = { html: 1, css: 1, js: 1 }

  var Live = {
    // performs a cycle per interval
    heartbeat: function () {
      if (document.body) {
        // make sure all resources are loaded on first activation
        if (!loaded) Live.loadresources()
        Live.checkForChanges()
      }
      setTimeout(Live.heartbeat, interval)
    },

    // loads all local css and js resources upon first activation
    loadresources: function () {
      // helper method to assert if a given url is local
      function isLocal(url) {
        var loc = document.location,
          reg = new RegExp(
            '^\\.|^/(?!/)|^[\\w]((?!://).)*$|' + loc.protocol + '//' + loc.host,
          )
        return url.match(reg)
      }

      // gather all resources
      var scripts = document.getElementsByTagName('script'),
        links = document.getElementsByTagName('link'),
        uris = []

      // track local js urls
      for (var i = 0; i < scripts.length; i++) {
        var script = scripts[i],
          src = script.getAttribute('src')
        if (src && isLocal(src)) uris.push(src)
        if (src && src.match(/\blive.js#/)) {
          for (var type in active)
            active[type] = src.match('[#,|]' + type) != null
          if (src.match('notify')) alert('Live.js is loaded.')
        }
      }
      if (!active.js) uris = []
      if (active.html) uris.push(document.location.href)

      // track local css urls
      for (var i = 0; i < links.length && active.css; i++) {
        var link = links[i],
          rel = link.getAttribute('rel'),
          href = link.getAttribute('href', 2)
        if (
          href &&
          rel &&
          rel.match(new RegExp('stylesheet', 'i')) &&
          isLocal(href)
        ) {
          uris.push(href)
          currentLinkElements[href] = link
        }
      }

      // initialize the resources info
      for (var i = 0; i < uris.length; i++) {
        var url = uris[i]
        Live.getHead(url, function (url, info) {
          resources[url] = info
        })
      }

      // add rule for morphing between old and new css files
      var head = document.getElementsByTagName('head')[0],
        style = document.createElement('style'),
        rule = 'transition: all .3s ease-out;'
      css = [
        '.livejs-loading * { ',
        rule,
        ' -webkit-',
        rule,
        '-moz-',
        rule,
        '-o-',
        rule,
        '}',
      ].join('')
      style.setAttribute('type', 'text/css')

      // Add nonce to style tag
      const nonce = document.querySelector(
        'meta[property="csp-nonce"]',
      )?.content
      style.setAttribute('nonce', nonce)

      head.appendChild(style)
      style.styleSheet
        ? (style.styleSheet.cssText = css)
        : style.appendChild(document.createTextNode(css))

      // yep
      loaded = true
    },

    // check all tracking resources for changes
    checkForChanges: function () {
      for (var url in resources) {
        if (pendingRequests[url]) continue

        Live.getHead(url, function (url, newInfo) {
          var oldInfo = resources[url],
            hasChanged = false
          resources[url] = newInfo
          for (var header in oldInfo) {
            if (header === 'Content-Security-Policy') continue // changes with each new request, due to nonce
            if (header === 'Content-Length') continue // can change because of gzip compression, we rely on etag anyway
            // do verification based on the header type
            var oldValue = oldInfo[header],
              newValue = newInfo[header],
              contentType = newInfo['Content-Type']
            switch (header.toLowerCase()) {
              case 'etag':
                if (!newValue) break
              // fall through to default
              default:
                hasChanged = oldValue != newValue
                if (hasChanged)
                  console.log(
                    'Live.js: ' +
                      header +
                      ' changed: ' +
                      oldValue +
                      ' != ' +
                      newValue,
                  )
                break
            }
            // if changed, act
            if (hasChanged) {
              Live.refreshResource(url, contentType)
              break
            }
          }
        })
      }
    },

    // act upon a changed url of certain content type
    refreshResource: function (url, type) {
      switch ((type || '').toLowerCase()) {
        // css files can be reloaded dynamically by replacing the link element
        case 'text/css':
          var link = currentLinkElements[url],
            html = document.body.parentNode,
            head = link.parentNode,
            next = link.nextSibling,
            newLink = document.createElement('link')

          html.className =
            html.className.replace(/\s*livejs\-loading/gi, '') +
            ' livejs-loading'
          newLink.setAttribute('type', 'text/css')
          newLink.setAttribute('rel', 'stylesheet')
          newLink.setAttribute('href', url + '?now=' + new Date() * 1)
          next ? head.insertBefore(newLink, next) : head.appendChild(newLink)
          currentLinkElements[url] = newLink
          oldLinkElements[url] = link

          // schedule removal of the old link
          Live.removeoldLinkElements()
          break

        // check if an html resource is our current url, then reload
        case 'text/html':
          if (url != document.location.href) return

        // local javascript changes cause a reload as well
        case 'text/javascript':
        case 'application/javascript':
        case 'application/x-javascript':
          document.location.reload()
      }
    },

    // removes the old stylesheet rules only once the new one has finished loading
    removeoldLinkElements: function () {
      var pending = 0
      for (var url in oldLinkElements) {
        // if this sheet has any cssRules, delete the old link
        try {
          var link = currentLinkElements[url],
            oldLink = oldLinkElements[url],
            html = document.body.parentNode,
            sheet = link.sheet || link.styleSheet,
            rules = sheet.rules || sheet.cssRules
          if (rules.length >= 0) {
            oldLink.parentNode.removeChild(oldLink)
            delete oldLinkElements[url]
            setTimeout(function () {
              html.className = html.className.replace(
                /\s*livejs\-loading/gi,
                '',
              )
            }, 100)
          }
        } catch (e) {
          pending++
        }
        if (pending) setTimeout(Live.removeoldLinkElements, 50)
      }
    },

    // performs a HEAD request and passes the header info to the given callback
    getHead: function (url, callback) {
      pendingRequests[url] = true
      var xhr = window.XMLHttpRequest
        ? new XMLHttpRequest()
        : new ActiveXObject('Microsoft.XmlHttp')
      xhr.open('HEAD', url, true)
      xhr.onreadystatechange = function () {
        delete pendingRequests[url]
        if (xhr.readyState == 4 && xhr.status != 304) {
          xhr.getAllResponseHeaders()
          var info = {}
          for (var h in headers) {
            var value = xhr.getResponseHeader(h)
            // adjust the simple Etag variant to match on its significant part
            if (h.toLowerCase() == 'etag' && value)
              value = value.replace(/^W\//, '')
            if (h.toLowerCase() == 'content-type' && value)
              value = value.replace(/^(.*?);.*?$/i, '$1')
            info[h] = value
          }
          callback(url, info)
        }
      }
      xhr.send()
    },
  }

  // start listening
  if (document.location.protocol != 'file:') {
    if (!window.liveJsLoaded) Live.heartbeat()

    window.liveJsLoaded = true
  } else if (window.console)
    console.log("Live.js doesn't support the file protocol. It needs http.")
})()
